"use strict"
import {Clock} from "../../lib/fui.js";
import ux from "./ux.js";
import * as game from "./game.js";

const FPS = 10
const State = {
    board: undefined,
    pcur: undefined,
    round: undefined,
    records: undefined,
    undos: undefined,
    redos: undefined,
    phase: undefined,
    ai_player: undefined,
    ai_in_think: undefined,
    ai_status: undefined,
    reset(){
        this.board = new game.Board(game.board_opening())
        this.pcur = game.TEBAN_SENTE
        this.round = 0
        this.records = []
        this.phase = new game.Phase(this.board)
        this.ai_player = [false, false]
        this.ai_in_think = false
        this.ai_status = ""
        this.undos = []
        this.redos = []
        return this
    },
    load(saved){
        this.board = saved[0]
        this.pcur = saved[1]
        this.round = saved[2]
        this.records = saved[3]
        this.phase = saved[4]
        return this
    },
    save(){
        return [new game.Board(this.board), this.pcur, this.round, this.records, this.phase]
    },
    move(m){
        let save = this.save()
        if (this.board.move(m)) {
            this.records.push(new game.Record(this.board.get(m.loc_to), m))
            this.pcur = game.teban_swap(this.pcur)
            this.round += 1
            this.undos.push(save)
            this.redos = []
            this.phase = new game.Phase(this.board)
            this.ai_in_think = false
            AiManager.send("valuation", this.board.to_str())
        } else {save = 0}
        return this
    },
    undo(n=1){
        for (; n > 0 && this.undos.length > 0; n -= 1) {
            this.redos.push(this.save())
            this.load(this.undos.pop())
        }
        return this
    },
    redo(n=1){
        for (; n > 0 && this.redos.length > 0; n -= 1) {
            this.undos.push(this.save())
            this.load(this.redos.pop())
        }
        return this
    },
}

const AiManager = (()=>{
    const me = {}
    const _observers = []
    function make(){
        const worker = new Worker("./ai-engine.js", {type: "module"})
        worker.onmessage = (e)=>{
            const msg = e.data
            _observers.forEach(R.applyTo(msg))
        }
        worker.onerror = (e)=>{
            console.log(`AI-Error(${e.filename}[${e.lineno}]): ${e.message}`, e)
            State.ai_in_think = false
            State.ai_player = [false, false]
        }
        return worker
    }
    let _worker = make()
    me.listen = (f)=>{
        _observers.push(f)
        return me
    }
    me.send  = (...tokens)=>{
        const text = R.map(String, tokens).join(' ')
        _worker.postMessage(text)
        return me
    }
    me.restart = ()=>{
        _worker.postMessage("reset")
        return me
    }
    return me
})()

;(()=>{
    State.reset()
    const io = {
        for_board(fn){
            for (const y of R.range(0, game.BOARD_SIZE)) {
                for (const x of R.range(0, game.BOARD_SIZE)) {
                    fn(y, x, State.board.get(game.Loc(y,x)))
                }
            }
            return this
        },
        get_piece(loc){
            return State.board.get(loc)
        },
        get_inhands(teban){
            return State.board.get_inhands(teban)
        },
        get_canmoves(loc){
            return State.board.canmoves(loc)
        },
        get_canputs(p){
            return State.board.canputs(p)
        },
        get_status(){
            return State.board.status()
        },
        phase(){
            return State.phase
        },
        pcur(){
            return State.pcur
        },
        round(){
            return State.round
        },
        record(r){
            const i = r-1
            if (i < State.records.length) {
                return State.records[i]
            }
        },
        ai_player(teban) {
            return State.ai_player[2-teban]
        },
        ai_player_set(teban, use) {
            State.ai_player[2-teban] = use
            return io
        },
        ai_in_think() {
            return State.ai_in_think
        },
        ai_status() {
            return State.ai_status
        },
        on_move(m, is_ai = false){
            if (State.ai_in_think && (! is_ai)) {return}
            if (State.board.status() != game.STATUS_NORMAL) {return}
            State.move(m)
        },
        on_cmd_undo(n=1){
            if (State.ai_in_think) {return}
            if (State.board.status() != game.STATUS_NORMAL) {
                State.ai_player = [false, false]
            }
            State.undo(n)
        },
        on_cmd_undo_to_first(){this.on_cmd_undo(State.undos.length)},
        on_cmd_redo(n){
            if (State.ai_in_think) {return}
            if (State.board.status() != game.STATUS_NORMAL) {return}
            State.redo(n)
        },
        on_cmd_redo_to_last(){this.on_cmd_redo(State.redos.length)},
        on_cmd_reset(){
            State.reset()
            AiManager.restart()
            console.clear()
        },
    }

    const AiMessage = {}
    AiMessage["ready"] = ()=>{}
    AiMessage["step"] = (a)=>{
        if (State.ai_in_think) {
            if (a == "pass") {
            } else {
                const m = game.Move_from_str(a)
                io.on_move(m, true)
            }
        }
    }
    AiMessage["ok"] = (...msg)=>{
        console.log("ok", ...msg)
    }
    AiManager.listen((msg)=>{
        const tokens = msg.split(' ')
        if (tokens.length > 0) {
            const head = tokens[0]
            if (AiMessage.hasOwnProperty(head)) {
                AiMessage[head](...(tokens.slice(1)))
            } else {
                console.log("[Unknown-Message]", msg)
            }
        }
    })

    const on_frame = ()=>{
        if (State.board.status() != game.STATUS_NORMAL) {return}
        if (((State.pcur == game.TEBAN_SENTE) && (State.ai_player[0])) || ((State.pcur == game.TEBAN_GOTE) && (State.ai_player[1]))) {
            State.ai_in_think = true
            AiManager.send("step", game.teban_to_str(State.pcur), State.board.to_str())
        }
    }

    ux.init(io)
    const clock = new Clock()
    clock.frame_join(on_frame).frame_join(ux.on_frame).start(FPS)
})()
